/**
 * 
 */
package org.swing.utility.common.stream;

import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;

import org.swing.utility.common.exception.SAMException;
import org.xerial.snappy.LoadSnappy;
import org.xerial.snappy.SnappyInputStream;

/**
 * @author lqnhu
 *
 */
public class SnappyLoader {
	private static final int SNAPPY_BLOCK_SIZE = 32768; // keep this as small as
														// can be without
														// hurting compression
														// ratio.
	private final Constructor<InputStream> SnappyInputStreamCtor;
	private final Constructor<OutputStream> SnappyOutputStreamCtor;
	public final boolean SnappyAvailable;
	// Force Snappy-java code to be loaded into executable jars.
	private final SnappyInputStream ignoreMe = null;
	// Force bcel to load Snappy.
	// private static final Class SnappyClass = SnappyInputStream.class;
	private static final boolean DefaultVerbosity = Boolean.valueOf(System
			.getProperty("snappy.loader.verbosity", "false"));

	public SnappyLoader() {
		this(DefaultVerbosity);
	}

	/**
	 * Constructs a new SnappyLoader which will check to see if snappy is
	 * available in the JVM/library path.
	 * 
	 * @param verbose
	 *            if true output a small number of debug messages to System.err
	 */
	public SnappyLoader(final boolean verbose) {
		Constructor<InputStream> inputStreamCtor = null;
		Constructor<OutputStream> outputStreamCtor = null;
		Class<Error> snappyErrorClass = null;
		if (java.lang.Boolean.valueOf(System.getProperty("snappy.disable",
				"false"))) {
			System.err.println("Snappy is disabled via system property.");
		} else {
			try {
				final Class<InputStream> snappyInputStreamClass = (Class<InputStream>) Class
						.forName("org.xerial.snappy.SnappyInputStream");
				final Class<OutputStream> snappyOutputStreamClass = (Class<OutputStream>) Class
						.forName("org.xerial.snappy.SnappyOutputStream");
				snappyErrorClass = (Class<Error>) Class
						.forName("org.xerial.snappy.SnappyError");
				inputStreamCtor = snappyInputStreamClass
						.getConstructor(InputStream.class);
				outputStreamCtor = snappyOutputStreamClass.getConstructor(
						OutputStream.class, Integer.TYPE);
			} catch (NoSuchMethodException e) { /* Do nothing. */
			} catch (ClassNotFoundException e) { /* Do nothing. */
			}
		}
		this.SnappyInputStreamCtor = inputStreamCtor;
		this.SnappyOutputStreamCtor = outputStreamCtor;
		if (this.SnappyInputStreamCtor != null
				&& this.SnappyOutputStreamCtor != null) {
			// Don't try to call any Snappy code until classes have been found
			// via reflection above.
			boolean tmpSnappyAvailable;
			try {
				if (!LoadSnappy.load()) {
					if (verbose)
						System.err.println("Snappy dll failed to load.");
					tmpSnappyAvailable = false;
				} else {
					if (verbose)
						System.err.println("Snappy stream classes loaded.");
					tmpSnappyAvailable = true;
				}
			} catch (Error e) {
				if (e.getClass().equals(snappyErrorClass)) {
					if (verbose)
						System.err.println("Snappy dll failed to load: "
								+ e.getMessage());
					tmpSnappyAvailable = false;
				} else {
					throw e;
				}
			}
			SnappyAvailable = tmpSnappyAvailable;
		} else {
			if (verbose)
				System.err.println("Snappy stream classes not loaded.");
			SnappyAvailable = false;
		}
	}

	/**
	 * Wrap an InputStream in a SnappyInputStream. If Snappy is not available
	 * will throw an exception.
	 */
	public InputStream wrapInputStream(final InputStream inputStream) {
		try {
			return SnappyInputStreamCtor.newInstance(inputStream);
		} catch (Exception e) {
			throw new SAMException("Error instantiating SnappyInputStream", e);
		}
	}

	/**
	 * Wrap an InputStream in a SnappyInputStream. If Snappy is not available
	 * will throw an exception.
	 */
	public OutputStream wrapOutputStream(final OutputStream outputStream) {
		try {
			return SnappyOutputStreamCtor.newInstance(outputStream,
					SNAPPY_BLOCK_SIZE);
		} catch (Exception e) {
			throw new SAMException("Error instantiating SnappyOutputStream", e);
		}
	}
}
